﻿using System;
using System.Linq;
using System.Linq.Expressions;

namespace Utils.Lib.Dynamics
{
    public static class DynamicQueryable
    {
        public static IQueryable<T> Where<T>(this IQueryable<T> source, string predicate, params object[] values)
        {
            return (IQueryable<T>) Where((IQueryable) source, predicate, values);
        }

        public static IQueryable Where(this IQueryable source, string predicate, params object[] values)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (predicate == null) throw new ArgumentNullException("predicate");
            var lambda = DynamicExpression.ParseLambda(source.ElementType, typeof (bool), predicate, values);
            return source.Provider.CreateQuery(
                Expression.Call(
                    typeof (Queryable), "Where",
                    new[] {source.ElementType},
                    source.Expression, Expression.Quote(lambda)));
        }

        public static IQueryable Select(this IQueryable source, string selector, params object[] values)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (selector == null) throw new ArgumentNullException("selector");
            var lambda = DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
            return source.Provider.CreateQuery(
                Expression.Call(
                    typeof (Queryable), "Select",
                    new[] {source.ElementType, lambda.Body.Type},
                    source.Expression, Expression.Quote(lambda)));
        }

        public static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> source, string ordering, params object[] values)
        {
            return (IQueryable<T>) OrderByDynamic((IQueryable) source, ordering, values);
        }

        public static IQueryable OrderByDynamic(this IQueryable source, string ordering, params object[] values)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (ordering == null) throw new ArgumentNullException("ordering");
            ParameterExpression[] parameters =
            {
                Expression.Parameter(source.ElementType, "")
            };
            var parser = new ExpressionParser(parameters, ordering, values);
            var orderings = parser.ParseOrdering();
            var queryExpr = source.Expression;
            var methodAsc = "OrderBy";
            var methodDesc = "OrderByDescending";
            foreach (var o in orderings)
            {
                queryExpr = Expression.Call(
                    typeof (Queryable), o.Ascending ? methodAsc : methodDesc,
                    new[] {source.ElementType, o.Selector.Type},
                    queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters)));
                methodAsc = "ThenBy";
                methodDesc = "ThenByDescending";
            }
            return source.Provider.CreateQuery(queryExpr);
        }

        public static IQueryable Take(this IQueryable source, int count)
        {
            if (source == null) throw new ArgumentNullException("source");
            return source.Provider.CreateQuery(
                Expression.Call(
                    typeof (Queryable), "Take",
                    new[] {source.ElementType},
                    source.Expression, Expression.Constant(count)));
        }

        public static IQueryable Skip(this IQueryable source, int count)
        {
            if (source == null) throw new ArgumentNullException("source");
            return source.Provider.CreateQuery(
                Expression.Call(
                    typeof (Queryable), "Skip",
                    new[] {source.ElementType},
                    source.Expression, Expression.Constant(count)));
        }

        public static IQueryable GroupBy(this IQueryable source, string keySelector, string elementSelector,
            params object[] values)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (keySelector == null) throw new ArgumentNullException("keySelector");
            if (elementSelector == null) throw new ArgumentNullException("elementSelector");
            var keyLambda = DynamicExpression.ParseLambda(source.ElementType, null, keySelector, values);
            var elementLambda = DynamicExpression.ParseLambda(source.ElementType, null, elementSelector, values);
            return source.Provider.CreateQuery(
                Expression.Call(
                    typeof (Queryable), "GroupBy",
                    new[] {source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type},
                    source.Expression, Expression.Quote(keyLambda), Expression.Quote(elementLambda)));
        }

        public static bool Any(this IQueryable source)
        {
            if (source == null) throw new ArgumentNullException("source");
            return (bool) source.Provider.Execute(
                Expression.Call(
                    typeof (Queryable), "Any",
                    new[] {source.ElementType}, source.Expression));
        }

        public static int Count(this IQueryable source)
        {
            if (source == null) throw new ArgumentNullException("source");
            return (int) source.Provider.Execute(
                Expression.Call(
                    typeof (Queryable), "Count",
                    new[] {source.ElementType}, source.Expression));
        }
    }
}